﻿// -
// <copyright file="HawaiiService.cs" company="Microsoft Corporation">
//    Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// -

namespace Hawaii.Services.Client
{
    using System;
    using System.Net;
    using System.IO;
    using System.Xml;
    using System.Text;
    using System.Diagnostics;

    /// <summary>
    /// A base class for all Hawaii Speech Service Clients to implement
    /// requesting the service, using BeginGetRequestStream and EndGetRequestStream
    /// APIs, and receiving the response from service, using BeginGetResponse and
    /// EndGetResponse APIs.
    /// </summary>
    public abstract class BaseClientProxy
    {
        /// <summary>
        /// Specifies the ErrorException during service calls.
        /// </summary>
        protected Exception ErrorException { get; set; }

        /// <summary>
        /// Specifies the HTTP method (GET, POST, PUT or DELETE).
        /// </summary>
        protected HttpMethod RequestMethod { get; set; }

        /// <summary>
        /// An abstract method to create a speech service uri object.
        /// This method is called when this class tries to contact the service.
        /// </summary>
        /// <returns></returns>
        protected abstract Uri ServiceUri();

        /// <summary>
        /// An abstract method to parse the result from the service.
        /// </summary>
        /// <param name="responseStream"></param>
        protected abstract void ParseOutput(Stream responseStream);

        /// <summary>
        /// An abstract method to get the POST data for a POST call.
        /// This method is called by this class to get data to send to the service.
        /// </summary>
        /// <returns></returns>
        protected abstract byte[] GetPostData();
        
        /// <summary>
        /// Default constructor
        /// </summary>
        public BaseClientProxy()
        {
            this.ErrorException = null;
            this.RequestMethod = HttpMethod.Get;
        }

        /// <summary>
        /// Overloaded constructor with the Http Method.
        /// </summary>
        /// <param name="requestMethod"></param>
        public BaseClientProxy(HttpMethod requestMethod)
        {
            this.ErrorException = null;
            this.RequestMethod = requestMethod;
        }

        /// <summary>
        /// Core method to contact the service. It is called by the 
        /// derived classes.
        /// </summary>
        /// <returns></returns>
        public bool ProcessRequest()
        {
            // -
            // Step 1: Obtain an HttpWebRequest object.
            // -
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(this.ServiceUri());
            request.Method = this.RequestMethod.ToString().ToUpper();

            try
            {
                if (this.RequestMethod == HttpMethod.Post)
                {
                    // -
                    // Step 1: Obtain an HttpWebRequest object.
                    // -
                    using (Stream stream = this.GetRequestStream(request))
                    {

                        if (stream == null)
                        {
                            throw new Exception("Null/Invalid request stream from server");
                        }

                        // -
                        // Get the input from the service client.
                        // -
                        byte[] inputBuffer = this.GetPostData();
                        Debug.Assert(inputBuffer != null && inputBuffer.Length != 0);

                        // -
                        // Step 3: POST data, for a POST request.
                        // -
                        if (inputBuffer != null && inputBuffer.Length != 0)
                        {
                            stream.Write(inputBuffer, 0, inputBuffer.Length);
                        }
                    }
                }
            }
            catch(Exception ex)
            {
                this.ErrorException = ex;
                return false;
            }

            // -
            // Get the speech service's response.
            // -
            try
            {
                // -
                // Step 4: Obtain an HttpWebResponse object.
                // -
                using (HttpWebResponse response = this.GetResponse(request))
                {
                    if (response == null)
                    {
                        throw new Exception("Null/Invalid response object from server");
                    }

                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        // -
                        // Step 5: Read the HTTP response data.
                        //
                        // Let the Service client consume the data.
                        // -
                        this.ParseOutput(response.GetResponseStream());
                    }
                }
            }
            catch (Exception ex)
            {
                this.ErrorException = ex;
                return false;
            }
            
            return true;
        }

        /// <summary>
        /// Mimics a synchronous GetRequestStream method on a given
        /// HttpWebRequest.
        /// </summary>
        /// <param name="request">The HTTP request to operate upon.</param>
        /// <returns>
        /// The stream to use to provide data for the HTTP request.
        /// </returns>
        public Stream GetRequestStream(HttpWebRequest request)
        {
            ServiceRequest state = new ServiceRequest(request);
            request.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), state);
            state.Waiter.WaitOne();

            return state.Stream;
        }

        /// <summary>
        /// Handles the completion of a BeginGetRequestStream call.
        /// </summary>
        /// <param name="asyncResult">
        /// The GetRequestStream associated with a completed call.
        /// </param>
        private void GetRequestStreamCallback(IAsyncResult asyncResult)
        {
            ServiceRequest state = (ServiceRequest)asyncResult.AsyncState;
            try
            {
                state.Stream = state.Request.EndGetRequestStream(asyncResult);
            }
            catch
            {
                state.Stream = null;
                return;
            }
            finally
            {
                state.Waiter.Set();
            }
        }

        /// <summary>
        /// Mimics a synchronous GetResponse method on a given HttpWebRequest.
        /// </summary>
        /// <param name="request">The HTTP request to operate on.</param>
        /// <returns>The HTTP response to the provided HTTP request.</returns>
        public HttpWebResponse GetResponse(HttpWebRequest request)
        {
            ServiceResponse state = new ServiceResponse(request);
            request.BeginGetResponse(new AsyncCallback(GetResponseCallback), state);
            state.Waiter.WaitOne();

            return state.Response;
        }

        /// <summary>
        /// Handles the completion of a BeginGetResponse call.
        /// </summary>
        /// <param name="asyncResult">
        /// The GetResponse associated with a completed call.
        /// </param>
        private void GetResponseCallback(IAsyncResult asyncResult)
        {
            ServiceResponse state =
                (ServiceResponse)asyncResult.AsyncState;
            try
            {
                state.Response =
                    (HttpWebResponse)state.Request.EndGetResponse(asyncResult);
            }
            catch
            {
                state.Response = null;
                return;
            }
            finally
            {
                state.Waiter.Set();
            }
        }
    }

}
